package com.newasia.server.common.interceptor.page;

import com.github.pagehelper.Page;
import com.newasia.server.dal.entitys.paramValue.HistoryParam;
import jodd.util.StringUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import static com.newasia.server.common.tool.CheckObjectIsNullUtils.checkObjIsNull;

/**
 * @author LiChong
 * @description 分页插件
 * @date 2020/5/25
 */

@Intercepts({
        @Signature(type= Executor.class, method = "query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}),
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Slf4j
public class PageQueryInterceptor implements Interceptor {

    private static final ThreadLocal<PageParam> localPage = new ThreadLocal<PageParam>();

    public static void setLocalPage(int pageNum,int pageSize,String orderBy,boolean isAscend){
        localPage.set(new PageParam(pageNum,pageSize,orderBy,isAscend));
    }

    public static PageParam getLocalPage(){
        PageParam page = localPage.get();
        return page;
    }

    public static PageParam removeLocalPage(){
        PageParam page = localPage.get();
        localPage.remove();
        return page;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if(checkObjIsNull(getLocalPage())){
            return invocation.proceed();
        }
        if (invocation.getTarget() instanceof StatementHandler) {
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
            // 分离代理对象链(由于目标类可能被多个插件拦截，从而形成多次代理，通过下面的两次循环
            // 可以分离出最原始的的目标类)
            while (metaObject.hasGetter("h")) {
                Object object = metaObject.getValue("h");
                metaObject = SystemMetaObject.forObject(object);
            }
            // 分离最后一个代理对象的目标类
            while (metaObject.hasGetter("target")) {
                Object object = metaObject.getValue("target");
                metaObject = SystemMetaObject.forObject(object);
            }
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
            BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
            // 分页参数作为参数对象parameterObject的一个属性
            String sql = boundSql.getSql();
            // 重写sql
            String pageSql = buildPageSql(sql, getLocalPage());
            //重写分页sql
            metaObject.setValue("delegate.boundSql.sql", pageSql);
            Connection connection = (Connection) invocation.getArgs()[0];
            // 重设分页参数里的总页数等
            setPageParameter(sql, connection, mappedStatement, boundSql, getLocalPage());
            // 将执行权交给下一个插件
            return invocation.proceed();
        } else if (invocation.getTarget() instanceof ResultSetHandler) {
            List result = (List)invocation.proceed();
            for (Object o:result){
                getLocalPage().add(o);
            }
            return removeLocalPage();
        }
        return null;
    }

    private String buildPageSql(String sql, PageParam page) {
        String order = "1";
        if (sql.contains("order by")){

        }
        StringBuilder builder = new StringBuilder();
        builder.append("select top ");
        builder.append(page.getPageSize());
        builder.append(" * from");
        builder.append("(select row_number()OVER(");
        builder.append(page.getOrderBy());
        builder.append(")as row_number,* from (");
        builder.append(sql);
        builder.append(") result)temp");
        builder.append(" where row_number > ");
        builder.append(page.getStartRow());
        return builder.toString();
    }

    /**
     * 获取总记录数
     *
     * @param sql
     * @param connection
     * @param mappedStatement
     * @param boundSql
     * @param page
     */
    private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
                                  BoundSql boundSql, PageParam page) {
        // 记录总记录数
        String countSql = "select count(0) from (" + sql + ") temp";
        PreparedStatement countStmt = null;
        ResultSet rs = null;
        try {
            countStmt = connection.prepareStatement(countSql);
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
            rs = countStmt.executeQuery();
            int totalCount = 0;
            if (rs.next()) {
                totalCount = rs.getInt(1);
            }
            page.setTotal(totalCount);
            int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
            page.setPages(totalPage);
        } catch (SQLException e) {
            log.error("Ignore this exception", e);
        } finally {
            try {
                rs.close();
            } catch (SQLException e) {
                log.error("Ignore this exception", e);
            }
            try {
                countStmt.close();
            } catch (SQLException e) {
                log.error("Ignore this exception", e);
            }
        }
    }

    /**
     *代入参数值
     * @author LiChong
     * @date 2020/5/27
     * @param  * @param ps
     * @param mappedStatement
     * @param boundSql
     * @param parameterObject
     * @return void
     */
    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                               Object parameterObject) throws SQLException {
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
